package p.jce;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.InvalidParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.AlgorithmParameterSpec;

public class RSignature {
	private static class Y extends Signature {

		private java.lang.reflect.InvocationHandler handler;

		protected Y(String algorithm, java.lang.reflect.InvocationHandler handler) {
			super(algorithm);
			this.handler = handler;
		}

		@Override
		protected Object engineGetParameter(String param) throws InvalidParameterException {
			return invoke(engineGetParameterS, param);
		}

		@Override
		protected AlgorithmParameters engineGetParameters() {
			return (AlgorithmParameters) invoke(engineGetParametersV);
		}

		@Override
		protected void engineInitSign(PrivateKey privateKey) throws InvalidKeyException {
			invoke(engineInitSignP, privateKey);
		}

		@Override
		protected void engineInitSign(PrivateKey privateKey, SecureRandom random) throws InvalidKeyException {
			invoke(engineInitSignPS, privateKey, random);
		}

		@Override
		protected void engineInitVerify(PublicKey publicKey) throws InvalidKeyException {
			invoke(engineInitVerifyP, publicKey);
		}

		@Override
		protected void engineSetParameter(AlgorithmParameterSpec params) throws InvalidAlgorithmParameterException {
			invoke(engineSetParameterAP, params);
		}

		@Override
		protected void engineSetParameter(String param, Object value) throws InvalidParameterException {
			invoke(engineSetParameterSO, param, value);
		}

		@Override
		protected byte[] engineSign() throws SignatureException {
			return (byte[]) invoke(engineSignV);
		}

		@Override
		protected int engineSign(byte[] outbuf, int offset, int len) throws SignatureException {
			return (Integer) invoke(engineSignBAII, outbuf, offset, len);
		}

		@Override
		protected void engineUpdate(byte b) throws SignatureException {
			invoke(engineUpdateB, b);
		}

		@Override
		protected void engineUpdate(byte[] b, int off, int len) throws SignatureException {
			invoke(engineUpdateBAII, b, off, len);
		}

		@Override
		protected void engineUpdate(ByteBuffer input) {
			invoke(engineUpdateBF, input);
		}

		@Override
		protected boolean engineVerify(byte[] sigBytes) throws SignatureException {
			return (Boolean) invoke(engineVerifyBA, sigBytes);
		}

		@Override
		protected boolean engineVerify(byte[] sigBytes, int offset, int length) throws SignatureException {
			return (Boolean) invoke(engineVerifyBAII, sigBytes, offset, length);
		}

		private Object invoke(Method m, Object... args) {
			try {
				return handler.invoke(this, m, args);
			} catch (Throwable e) {
				throw new RuntimeException(e);
			}
		}

	}

	final private static Method engineGetParameterS = getMethod("engineGetParameter", String.class);
	private static final Method engineGetParametersV = getMethod("engineGetParameters");
	final private static Method engineInitSignP = getMethod("engineInitSign", PrivateKey.class);
	private static final Method engineInitSignPS = getMethod("engineInitSign", PrivateKey.class, SecureRandom.class);
	final private static Method engineInitVerifyP = getMethod("engineInitVerify", PublicKey.class);
	private static final Method engineSetParameterAP = getMethod("engineSetParameter", AlgorithmParameterSpec.class);
	final private static Method engineSetParameterSO = getMethod("engineSetParameter", String.class, Object.class);
	private static final Method engineSignBAII = getMethod("engineSign", byte[].class, int.class, int.class);
	final private static Method engineSignV = getMethod("engineSign");
	final private static Method engineUpdateB = getMethod("engineUpdate", byte.class);
	final private static Method engineUpdateBAII = getMethod("engineUpdate", byte[].class, int.class, int.class);
	private static final Method engineUpdateBF = getMethod("engineUpdate", ByteBuffer.class);
	final private static Method engineVerifyBA = getMethod("engineVerify", byte[].class);
	private static final Method engineVerifyBAII = getMethod("engineVerify", byte[].class, int.class, int.class);

	// public static abstract class X extends Signature {
	//
	// protected X() {
	// super("DEFAULT");
	// }
	// }
	// static final Factory fa;
	// static {
	// Enhancer enhancer = new Enhancer();
	// enhancer.setSuperclass(X.class);
	// enhancer.setCallbackType(InvocationHandler.class);
	// enhancer.setCallback(new InvocationHandler() {
	// @Override
	// public Object invoke(Object proxy, Method method, Object[] args) throws
	// Throwable {
	// return null;
	// }
	// });
	//
	// enhancer.setUseFactory(true);
	//
	// fa = (Factory) enhancer.create();
	// }
	//
	// private static Signature createSignature(String algorithm, final
	// java.lang.reflect.InvocationHandler handler) {
	// return (Signature) fa.newInstance(new InvocationHandler() {
	// @Override
	// public Object invoke(Object proxy, Method method, Object[] args) throws
	// Throwable {
	// method.setAccessible(true);
	// return handler.invoke(proxy, method, args);
	// }
	// });
	// }

	private static Signature createSignature(String algorithm, final java.lang.reflect.InvocationHandler handler) {
		return new Y(algorithm, handler);
	}

	public static Signature getInstance(String algorithm) throws NoSuchAlgorithmException {
		try {
			return getInstance(algorithm, "BC");
		} catch (NoSuchProviderException e) {
			throw new RuntimeException(e);
		}
	}

	public static Signature getInstance(String algorithm, Provider provider) throws NoSuchAlgorithmException {
		return getInstance(algorithm);
	}

	public static Signature getInstance(String algorithm, String provider) throws NoSuchAlgorithmException,
			NoSuchProviderException {
		if ("BC".equals(provider)) {
			try {
				final RSignatureSpi sSpi = BC.newInstance("Signature", algorithm);
				return createSignature(algorithm, new java.lang.reflect.InvocationHandler() {

					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						method.setAccessible(true);
						return method.invoke(sSpi, args);
					}
				});
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
		}
		return Signature.getInstance(algorithm, provider);
	}

	private static Method getMethod(String name, Class<?>... args) {
		try {
			Method method = RSignatureSpi.class.getDeclaredMethod(name, args);
			return method;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

}
